package com.life.ai.server.modules.system.service.impl;

import cn.dev33.satoken.secure.SaSecureUtil;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.life.ai.server.common.constants.CacheConstants;
import com.life.ai.server.common.constants.Constants;
import com.life.ai.server.common.exception.ServiceException;
import com.life.ai.server.common.utils.RedisUtils;
import com.life.ai.server.common.utils.ServletUtils;
import com.life.ai.server.framework.config.properties.SystemProperties;
import com.life.ai.server.modules.develop.service.SysLoginLogService;
import com.life.ai.server.modules.system.entity.SysUser;
import com.life.ai.server.modules.system.enums.UserStatusEnum;
import com.life.ai.server.modules.system.model.dto.LoginParamDTO;
import com.life.ai.server.modules.system.model.vo.SysUserInfoVO;
import com.life.ai.server.modules.system.service.SysLoginService;
import com.life.ai.server.modules.system.service.SysUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.Duration;
import java.util.Objects;
import java.util.function.Supplier;

/**
 * SysLoginServiceImpl
 *
 * @author kylewka
 * @since 2023/11/14
 */
@Slf4j
@Service
public class SysLoginServiceImpl implements SysLoginService {

    @Resource
    private SysUserService     userService;
    @Resource
    private SysLoginLogService loginLogService;

    @Override
    public SaTokenInfo login(LoginParamDTO loginParam) {
        try {
            // 验证码校验
            validateCaptcha(loginParam.getUuid(), loginParam.getCode());
            // 用户名查询用户信息
            SysUser sysUser = loadUserByUsername(loginParam.getUsername());

            // 校验登录密码
            String loginPwd = SaSecureUtil.md5BySalt(loginParam.getPassword(), sysUser.getSalt());
            checkLogin(loginParam.getUsername(), () -> !Objects.equals(loginPwd, sysUser.getPassword()));

            // 执行登录
            StpUtil.login(sysUser.getId());

            // 登录成功日志
            loginLogService.saveLoginRecord(loginParam.getUsername(), Constants.LOGIN_SUCCESS, "登录成功");
            return StpUtil.getTokenInfo();
        } catch (Exception e) {
            // 登录失败日志
            loginLogService.saveLoginRecord(loginParam.getUsername(), Constants.LOGIN_FAIL, e.getMessage());
            throw e;
        }
    }


    private SysUser loadUserByUsername(String username) {
        SysUser user = userService.lambdaQuery().eq(SysUser::getUsername, username).one();
        if (ObjectUtil.isNull(user)) {
            log.info("登录用户：{} 不存在.", username);
            throw new ServiceException("登录用户：{} 不存在", username);
        }
        if (UserStatusEnum.DISABLE.getCode().equals(user.getStatus())) {
            log.info("登录用户：{} 已被停用.", username);
            throw new ServiceException("登录用户：{} 已被停用", username);
        }
        return user;
    }

    /**
     * 校验验证码
     *
     * @param uuid 唯一标识
     * @param code 验证码
     */
    public void validateCaptcha(String uuid, String code) {
        boolean captchaEnabled = SystemProperties.enableLoginCaptcha;
        if (captchaEnabled) {
            String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid;
            String captcha = RedisUtils.get(verifyKey);
            if (captcha == null) {
                throw new ServiceException("验证码已过期");
            }
            RedisUtils.del(verifyKey);
            if (!code.equalsIgnoreCase(captcha)) {
                throw new ServiceException("验证码错误");
            }
        }
    }

    /**
     * 登录校验
     */
    private void checkLogin(String username, Supplier<Boolean> supplier) {
        long maxRetryCount = SystemProperties.loginMaxRetryCount; // 重试次数
        long lockTime = SystemProperties.loginLockTime; // 锁定时长

        // 根据用户名+IP 限制登录重试次数
        String ip = ServletUtils.getClientIP();
        String errorKey = CacheConstants.PWD_ERR_CNT_KEY + username + StrUtil.AT + ip;

        // 获取用户登录错误次数，默认为0
        int errorNumber = ObjectUtil.defaultIfNull(RedisUtils.get(errorKey), 0);
        if (errorNumber >= maxRetryCount) {
            throw new ServiceException("密码输入错误{}次，帐户锁定{}分钟", maxRetryCount, lockTime);
        }
        // 用户名密码错误
        if (supplier.get()) {
            errorNumber++;
            RedisUtils.set(errorKey, errorNumber, Duration.ofMinutes(lockTime).getSeconds());
            // 达到规定错误次数 则锁定登录
            if (errorNumber >= maxRetryCount) {
                throw new ServiceException("密码输入错误{}次，帐户锁定{}分钟", maxRetryCount, lockTime);
            } else {
                // 未达到规定错误次数
                throw new ServiceException("密码输入错误{}次", errorNumber);
            }
        }
    }

    @Override
    public void logout() {
        StpUtil.logout();
    }

    /**
     * 当前登录用户信息
     */
    @Override
    public SysUserInfoVO currentUserInfo() {
        long userId = StpUtil.getLoginIdAsLong();
        return userService.getUserInfoByUserId(userId);
    }
}
